#include <stdio.h>
#include <stdlib.h>
#include <elf.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>

#include <openssl/des.h>

#define KEY_SIZE (24)
#define DES_BLOCK_ALIGN (sizeof(DES_cblock))

#define ALIGN(val, align) (((val) + (align) - 1) & ~((align) - 1))

/*
 * _encrypt, encrypt data with DES_ecb3_encrypt
 * return true if succeeds, or false
 */
bool _encrypt(const unsigned char* in, unsigned int in_size, unsigned char* out, unsigned int *out_size, unsigned char key[KEY_SIZE])
{
	DES_key_schedule ks1, ks2, ks3;
	int i = 0;
	unsigned int len;
	char* src = NULL;
	char ch = '\0';

    if(!in || !out || !out_size || !key)
    {
        return false;
    }

	if(in_size == 0)
    {
		return false;
    }

	DES_set_key_unchecked((DES_cblock*)(&key[0]), &ks1);
	DES_set_key_unchecked((DES_cblock*)(&key[DES_BLOCK_ALIGN]), &ks2);
	DES_set_key_unchecked((DES_cblock*)(&key[2 * DES_BLOCK_ALIGN]), &ks3);

	src = malloc(in_size + 8);
	if(!src)
		return false;

	bzero(src, in_size + 8);
	memcpy(src, in, in_size);
	len = ALIGN(in_size, DES_BLOCK_ALIGN) + ((in_size % DES_BLOCK_ALIGN == 0) ? DES_BLOCK_ALIGN: 0);
	ch = DES_BLOCK_ALIGN - (in_size % DES_BLOCK_ALIGN);

	memset(src + in_size, ch, len - in_size);
	for(i = 0; i  < len; i += DES_BLOCK_ALIGN)
	{
		DES_ecb3_encrypt((DES_cblock*)(src + i), 
				 (DES_cblock*)(out + i),
				 &ks1, &ks2, &ks3,
				 DES_ENCRYPT);
	}

	if(out_size)
		*out_size = len;
	free(src);

	return true;
}

/*
 * _decrypt, decrypt data with DES_ecb3_encrypt
 * return true if succeeds, or false
 */
bool _decrypt(const unsigned char* in, unsigned int in_size, unsigned char* out, unsigned int *out_size, unsigned char key[KEY_SIZE])
{
	DES_key_schedule ks1, ks2, ks3;
	int i = 0;
	unsigned int len;
	char* src = NULL;
	char* tgt = NULL;
	char rest;
	bool ret = false;

    if(!in || !out || !out_size || !key)
    {
        return false;
    }

	if(in_size <= 0)
    {
		return false;
    }

	DES_set_key_unchecked((DES_cblock*)(&key[0]), &ks1);
	DES_set_key_unchecked((DES_cblock*)(&key[DES_BLOCK_ALIGN]), &ks2);
	DES_set_key_unchecked((DES_cblock*)(&key[2 * DES_BLOCK_ALIGN]), &ks3);

	src = malloc(in_size);
	if(!src)
		goto decrypt_out;
    bzero(src, in_size);
	tgt = malloc(in_size);
	if(!tgt)
		goto decrypt_out;
    bzero(tgt, in_size);


	memset(src, 0, in_size);
	memset(tgt, 0, in_size);

	memcpy(src, in, in_size);
	for(i = 0; i  < in_size; i += DES_BLOCK_ALIGN)
	{
		DES_ecb3_encrypt((DES_cblock*)(src + i), 
				 (DES_cblock*)(tgt + i),
				 &ks1, &ks2, &ks3,
				 DES_DECRYPT);
	}

	rest = tgt[in_size - 1];
	for(i = in_size - rest; i < in_size; ++i)
	{
		if(tgt[i] != rest)
			goto decrypt_out;
	}


	len = in_size - rest;

	if(out_size)
		*out_size = len;

	memcpy(out, tgt, len);
	ret = true;

decrypt_out:

	if(src)
		free(src);
	if(tgt)
		free(tgt);
	return ret;
	
}
